<?php
// *** Tangra (Application Framework and Tools for PHP)
// $Id$
//


require_once($PATHS->get_hidden().'inc/_sys/site_dbc_pnt.class.php');


class Session_In_DB extends Tangra_Class {
	private $dbc;
	private $table;
	private $notify_on_expire = array();
	private $force_garbage_collection;
	private $probability;
	private $divisor;


	function __construct($dbc, $table = 'sessions', $force_garbage_collection = 0, $probability = 1, $divisor = 100) {
		$this->set_dbc($dbc);
		$this->set_table($table);
		$this->set_force_garbage_collection($force_garbage_collection);
		$this->set_probability($probability);
		$this->set_divisor($divisor);
	}


	public function open($save_path, $session_name) {
		$GLOBALS['SESSION_DBC'] = $this->get_dbc();

		return true;
	}


	public function close() {
		if (session_id()) {
			$dbc = $this->get_dbc();
			$dbc->disconnect();
		}

		return true;
	}


	public function read($id) {
		$this->gc_initiator();

		$ts = $this->get_current_time();
		$table = $this->get_table();
		$sql = "SELECT sessdata FROM $table WHERE sesskey = '$id' AND expiry >= $ts";

		$dbc = $this->get_dbc();
		$rez = $dbc->execute($sql);

		if (!$rez->is_eof()) {
			$rez_obj = $rez->fetch_object();
			$ret = rawurldecode($rez_obj->SESSDATA);
		} else {
			$ret = '';
		}

		return $ret;
	}


	public function write($id, $sess_data) {
		$sess_data = rawurlencode($sess_data);

		$table = $this->get_table();
		$now = time();

		$expiry = $now + ini_get('session.gc_maxlifetime');
		$table = $this->get_table();

		$sql = "SELECT count(*) as c FROM $table WHERE sesskey = '$id'";
		$dbc = $this->get_dbc();

		$rez = $dbc->execute($sql);
		$rez_obj = $rez->fetch_object();

		if ($rez_obj->C == 0) {
			$sql = "insert into $table (expiry, "
										."sessdata, "
										."sesskey, "
										."created, "
										."modified) "
									."values "
										."('$expiry', "
										."'$sess_data', "
										."'$id', "
										."$now, "
										."$now"
										.") ";
		} else {
			$sql = "update $table set "
									."expiry = '$expiry', "
									."sessdata = '$sess_data', "
									."sesskey = '$id', "
									."modified = $now "
									."where sesskey = '$id'";
		}

		$rez = $dbc->execute($sql);

		return true;
	}


	public function destroy($id) {
		$dbc = $this->get_dbc();

		$rez = $this->_destroy($id, $dbc);

		return true;
	}


	private function _destroy($id, DBAL_Conn $dbc) {
		$this->notify_on_expire($id);

		$table = $this->get_table();

		$sql = "DELETE FROM $table WHERE sesskey = '$id'";

		$dbc->execute($sql);

		return true;
	}


	public function gc($maxlifetime = 0) {
		$ts = $this->get_current_time();
		$table = $this->get_table();

		$sql = "SELECT sesskey FROM $table WHERE expiry < $ts";
		$dbc = $this->get_dbc();
		$rez = $dbc->execute($sql);

		if (!$rez->is_eof()) {
			while($rez_obj = $rez->fetch_object()) {
				$this->_destroy($rez_obj->SESSKEY, $dbc);
			}
		}
	}


	public function set_dbc($dbc) {
		$this->dbc = $dbc;
	}


	public function get_dbc() {
		return $this->dbc;
	}


	public function set_table($table) {
		$this->table = $table;
	}


	public function get_table() {
		return $this->table;
	}


	public function set_probability($probability) {
		tangra_if_not_int_throw_e($probability);

		$this->probability = abs($probability);
	}


	public function get_probability() {
		return $this->probability;
	}


	public function set_divisor($divisor) {
		tangra_if_not_int_throw_e($divisor);

		$this->divisor = abs($divisor);
	}


	public function get_divisor() {
		return $this->divisor;
	}


	public function set_notify_on_expire($notify_on_expire) {
		$this->notify_on_expire = $notify_on_expire;
	}


	public function get_notify_on_expire() {
		return $this->notify_on_expire;
	}


	public function set_force_garbage_collection($force_garbage_collection) {
		$this->force_garbage_collection = $force_garbage_collection ? true : false;
	}


	public function get_force_garbage_collection() {
		return $this->force_garbage_collection;
	}


	private function notify_on_expire($sess_id) {
		if (is_array($this->get_notify_on_expire())) {
			foreach($this->get_notify_on_expire() as $object => $method) {
				call_user_func(array($object, $method), $sess_id);
			}
		} else {
			call_user_func($this->get_notify_on_expire(), $sess_id);
		}
	}


	/**
	 * Returns current time.
	 *
	 * In case of web farms this method have to be overloaded and to select current time from DB.
	 * Use appropriate section for your db provider (commented in body function bellow)
	 *
	 * @return integer Unix timespamp
	 */
	protected function get_current_time() {
		/*
		$dbc = $this->get_dbc();
		$sql = "select round(date_part('epoch',now())) as ts"; //postgre
		$sql = "select unix_timestamp() as ts"; //mysql
		$rez = $dbc->execute($sql);
		$rez_obj = $rez->fetch_object();
		$ret = $rez_obj->TS;
		*/



		return time();
	}


	protected function gc_initiator() {
		if ($this->get_force_garbage_collection()) {
			$probability = $this->get_probability();
			$divisor = $this->get_divisor();

			if ($divisor > 0) {
				$coefficient = $probability /$divisor;
				if ($coefficient >= 1) {
					$this->gc();
				} else {
					$range_max = (int) (1 / $coefficient);

					$random = rand(1, $range_max);

					if ($random == $range_max) {
						$this->gc();
					}
				}
			}
		}
	}
}


